import { BsonIgnore, GetTypeOf, IsInstanceOf } from "../../../Decorator/Decorator";
import { Dictionary } from "../../../Support/Share/Lib/Dictionary";
import { DisposeObject } from "../../Object/DisposeObject";
import { EventSystem } from "../EventSystem/EventSystem";
import { IAddComponent } from "../EventSystem/IAddComponentSystem";
import { IDestroy } from "../EventSystem/IDestroySystem";
import { IdGenerater } from "../IdGenerater/IdGenerater";
import { Root } from "./Root";

export enum EntityStatus {
    None = 0,
    IsFromPool = 1,
    IsRegister = 1 << 1,
    IsComponent = 1 << 2,
    IsCreated = 1 << 3,
    IsNew = 1 << 4
}

export class Entity extends DisposeObject {

    @BsonIgnore()
    private status: EntityStatus = EntityStatus.None;

    @BsonIgnore()
    public get IsFromPool(): boolean {
        return (this.status & EntityStatus.IsFromPool) == EntityStatus.IsFromPool;
    }
    public set IsFromPool(value: boolean) {
        if (this.IsFromPool == value) {
            return;
        }
        if (value) {
            this.status |= EntityStatus.IsFromPool;
        } else {
            this.status &= ~EntityStatus.IsFromPool;
        }
    }

    @BsonIgnore()
    public get IsRegister(): boolean {
        return (this.status & EntityStatus.IsRegister) == EntityStatus.IsRegister;
    }
    public set IsRegister(value: boolean) {
        if (this.IsRegister == value) {
            return;
        }
        if (value) {
            this.status |= EntityStatus.IsRegister;
        } else {
            this.status &= ~EntityStatus.IsRegister;
        }

        if (!value) {
            Root.Instance.Remove(this.InstanceId);
        } else {
            Root.Instance.Add(this);
            EventSystem.Instance.RegisterSystem(this, value);
        }
    }

    @BsonIgnore()
    public get IsComponent(): boolean {
        return (this.status & EntityStatus.IsComponent) == EntityStatus.IsComponent;
    }
    public set IsComponent(value: boolean) {
        if (this.IsComponent == value) {
            return;
        }
        if (value) {
            this.status |= EntityStatus.IsComponent;
        } else {
            this.status &= ~EntityStatus.IsComponent;
        }
    }

    @BsonIgnore()
    public get IsCreated(): boolean {
        return (this.status & EntityStatus.IsCreated) == EntityStatus.IsCreated;
    }
    public set IsCreated(value: boolean) {
        if (this.IsCreated == value) {
            return;
        }
        if (value) {
            this.status |= EntityStatus.IsCreated;
        } else {
            this.status &= ~EntityStatus.IsCreated;
        }
    }

    @BsonIgnore()
    public get IsNew(): boolean {
        return (this.status & EntityStatus.IsNew) == EntityStatus.IsNew;
    }
    public set IsNew(value: boolean) {
        if (this.IsNew == value) {
            return;
        }
        if (value) {
            this.status |= EntityStatus.IsNew;
        } else {
            this.status &= ~EntityStatus.IsNew;
        }
    }

    /******************************************************************************************************
     * 
     * 
     * 
     *****************************************************************************************************/

    private _Id!: number;
    public get Id(): number {
        return this._Id;
    }
    public set Id(value: number) {
        this._Id = value;
    }

    @BsonIgnore()
    private _InstanceId!: number;
    public get InstanceId(): number {
        return this._InstanceId;
    }
    protected set InstanceId(value: number) {
        this._InstanceId = value;
    }

    @BsonIgnore()
    protected parent!: Entity;
    public get Parent(): Entity {
        return this.parent;
    }
    public set Parent(value: Entity) {
        if (value == null) {
            throw new Error(`can't set parent null: ${typeof this}`);
        }
        if (value == this) {
            throw new Error(`can't set parent self: ${typeof this}`);
        }
        this.parent = value;
        this.IsComponent = false;
        this.parent.AddToChildren(this);
        this.Domain = this.parent.Domain;
    }

    private set componentParent(value: Entity) {
        if (this.Parent != null) {
            throw new Error(`Component parent is not null: ${this.constructor.name}`);
        }

        this.Parent = value;
        this.IsComponent = true;
        this.Parent.AddToComponents(this);
        this.Domain = this.Parent.Domain;
    }

    public GetParent<T extends Entity>(): T {
        return this.Parent as T;
    }

    @BsonIgnore()
    protected domain!: Entity;
    public get Domain(): Entity {
        return this.domain;
    }
    public set Domain(value: Entity) {
        if (value == null) {
            throw new Error(`domain can't set null: ${typeof this}`);
        }
        if (value == this.Domain) {
            return;
        }
        let preDomain = this.Domain;
        this.domain = value;

        if (preDomain == null) {
            this.InstanceId = IdGenerater.Instance.GenerateInstanceId();
            this.IsRegister = true;
        }
    }

    @BsonIgnore()
    private children!: Dictionary<number, Entity>;
    public get Children(): Dictionary<number, Entity> {
        if (!this.children) {
            this.children = new Dictionary<number, Entity>();
        }
        return this.children;
    }

    AddChild<T extends Entity>(clazz: T): T;
    AddChild<T extends Entity>(clazz: typeof Entity): T;
    AddChild<T extends Entity, A>(clazz: typeof Entity, a: A): T;
    AddChild<T extends Entity, A, B>(clazz: typeof Entity, a: A, b: B): T;
    AddChild<T extends Entity, A, B, C>(clazz: typeof Entity, a: A, b: B, c: C): T;
    AddChild<T extends Entity, A, B, C, D>(clazz: typeof Entity, a: A, b: B, c: C, d: D): T;
    AddChild<T extends Entity, A, B, C, D>(clazz: typeof Entity, a?: A, b?: B, c?: C, d?: D): T {
        if (clazz instanceof Entity) {
            clazz.Parent = this;
            return <T><unknown>clazz;
        } else {
            let component: T = <T>Entity.Create(clazz);
            component.Id = IdGenerater.Instance.GenerateId();
            component.Parent = this;

            EventSystem.Instance.Awake(component, a, b, c, d);

            return component;
        }
    }

    AddChildWithId<T extends Entity>(clazz: typeof Entity, id: number): T;
    AddChildWithId<T extends Entity, A>(clazz: typeof Entity, id: number, a: A): T;
    AddChildWithId<T extends Entity, A, B>(clazz: typeof Entity, id: number, a: A, b: B): T;
    AddChildWithId<T extends Entity, A, B, C>(clazz: typeof Entity, id: number, a: A, b: B, c: C): T;
    AddChildWithId<T extends Entity, A, B, C, D>(clazz: typeof Entity, id: number, a: A, b: B, c: C, d: D): T;
    AddChildWithId<T extends Entity, A, B, C, D>(clazz: typeof Entity, id: number, a?: A, b?: B, c?: C, d?: D): T {
        let component: T = <T>Entity.Create(clazz);
        component.Id = id;
        component.Parent = this;

        EventSystem.Instance.Awake(component, a, b, c, d);

        return component;
    }

    public AddToChildren(entity: Entity) {
        this.Children.Add(entity.Id, entity);
    }

    public RemoveFromChildren(entity: Entity) {
        if (this.Children == null) {
            return;
        }
        this.Children.Remove(entity.Id);
    }

    @BsonIgnore()
    private _components!: Dictionary<string, Entity>;
    public get components(): Dictionary<string, Entity> {
        if (!this._components) {
            this._components = new Dictionary<string, Entity>();
        }
        return this._components;
    }

    public AddToComponents(componet: Entity) {
        this.components.Add(componet.clazzType, componet);
    }

    public RemoveFromComponents(componet: Entity) {
        this.components.Remove(componet.clazzType);
    }

    public GetChild<T extends Entity>(id: number): T {
        if (this.Children == null) {
            return;
        }
        let child = this.Children.get(id);
        return child as T;
    }

    public RemoveChild(id: number) {
        if (this.Children == null) {
            return;
        }
        let child = this.Children.get(id);
        if (!child) {
            return;
        }
        this.Children.delete(id);
        child.Dispose();
    }

    public RemoveComponent<K extends Entity>(clazz: typeof Entity): void;
    public RemoveComponent(entity: Entity): void;
    public RemoveComponent<K extends Entity>(clazz: typeof Entity | Entity): void {
        if (this.IsDisposed) {
            return;
        }
        if (this.components == null) {
            return;
        }
        let c: Entity = null;
        if (clazz instanceof Entity) {
            c = this.GetComponent(clazz);
            if (c == null) {
                return;
            }
            if (c.InstanceId != clazz.InstanceId) {
                return;
            }
        } else {
            c = this.GetComponent<K>(clazz);
            if (c == null) {
                return;
            }
        }
        this.RemoveFromComponents(c);
        c.Dispose();
    }

    public GetComponent<T extends Entity>(clazz: typeof Entity): T;
    public GetComponent(entity: Entity): Entity;
    public GetComponent<T extends Entity>(clazz: typeof Entity | Entity): T {
        if (clazz instanceof Entity) {
            return this.components.TryGetValue(GetTypeOf(clazz)) as T;
        } else {
            return this.components.TryGetValue(clazz.name) as T;
        }
    }

    private static Create(clazz: typeof Entity): Entity {
        let component = new clazz();
        component.Id = 0;
        return component;
    }

    AddComponentInstance(component: Entity) {
        if (this.components != null && this.components.ContainsKey(component.clazzType)) {
            throw new Error(`entity already has component: ${component.clazzType}`);
        }
        component.componentParent = this;
        if (IsInstanceOf(this, IAddComponent)) {
            // EventSystem.Instance.AddComponent(this, component);
        }
        return component;
    }

    AddComponent<T extends Entity>(clazz: typeof Entity): T;
    AddComponent<T extends Entity, A>(clazz: typeof Entity, a: A,): T;
    AddComponent<T extends Entity, A, B,>(clazz: typeof Entity, a: A, b: B): T;
    AddComponent<T extends Entity, A, B, C>(clazz: typeof Entity, a: A, b: B, c: C): T;
    AddComponent<T extends Entity, A, B, C, D>(clazz: typeof Entity, a: A, b: B, c: C, d: D): T;
    AddComponent<T extends Entity, A, B, C, D>(clazz: typeof Entity, a?: A, b?: B, c?: C, d?: D): T {
        if (this.components != null && this.components.ContainsKey(clazz.name)) {
            throw new Error(`entity already has component: ${clazz.name}`);
        }
        let component: T = <T>Entity.Create(clazz);
        component.Id = this.Id;
        component.componentParent = this;
        EventSystem.Instance.Awake(component, a, b, c, d);

        this.AddToComponents(component);

        return component;
    }

    /******************************************************************************************************
     * 
     * 
     * 
     *****************************************************************************************************/

    public get IsDisposed() {
        return false;
    }

    public Dispose() {
        if (this.IsDisposed) {
            return;
        }

        this.IsRegister = false;
        this.InstanceId = 0;

        // 清理Children
        if (this.children != null) {
            for (let [, child] of this.children) {
                child.Dispose();
            }

            this.children.Clear();
            // ObjectPool.Instance.Recycle(this.children);
            this.children = null;

            // if (this.childrenDB != null) {
            //     this.childrenDB.Clear();
            //     // 创建的才需要回到池中,从db中不需要回收
            //     if (this.IsNew) {
            //         ObjectPool.Instance.Recycle(this.childrenDB);
            //         this.childrenDB = null;
            //     }
            // }
        }

        // 清理Component
        if (this.components != null) {
            for (let [, component] of this.components) {
                component.Dispose();
            }

            this.components.Clear();
            // ObjectPool.Instance.Recycle(this.components);
            this._components = null;

            // 创建的才需要回到池中,从db中不需要回收
            // if (this.componentsDB != null) {
            //     this.componentsDB.Clear();
            //     if (this.IsNew) {
            //         ObjectPool.Instance.Recycle(this.componentsDB);
            //         this.componentsDB = null;
            //     }
            // }
        }

        // 触发Destroy事件
        if (IsInstanceOf(this, IDestroy)) {
            EventSystem.Instance.DestoryEntity(this);
        }

        this.domain = null;

        if (this.parent != null && !this.parent.IsDisposed) {
            if (this.IsComponent) {
                this.parent.RemoveComponent(this);
            }
            else {
                this.parent.RemoveFromChildren(this);
            }
        }

        this.parent = null;

        super.Dispose();

        // if (this.IsFromPool) {
        //     ObjectPool.Instance.Recycle(this);
        // }
        this.status = EntityStatus.None;
    }

}